/*
 * conf.c
 *
 * Copyright (C) 2011 - Dr.NP
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @version conf.c 1.0.0
 * @package bsp
 * @author Dr.NP <np@bsgroup.org>
 * @update 03/18/2011
 */

/**
 * Config file paraser
 *
 * === CHANGELOG ===
 * [03/18/2011] - Creation
 */

#include "bsp.h"

#include "bsp_core.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>

void conf_parse_line(char *);

struct param_t conf_list[CONF_HASH_SIZE];
struct param_t **conf_index;
pthread_mutex_t conf_lock = PTHREAD_MUTEX_INITIALIZER;
int conf_total, conf_index_size;

/**
 * Initialization of configuration parser
 *
 * @return int                                  Return value
 */
int conf_init()
{
    memset(conf_list, 0, CONF_HASH_SIZE * sizeof(struct param_t));
    conf_index = malloc(sizeof(struct param_t *) * CONF_INDEX_INITIAL);

    if (!conf_index)
    {
        return RTN_ERROR_MEMORY_ALLOC;
    }

    conf_total = 0;
    conf_index_size = CONF_INDEX_INITIAL;
    
    return RTN_SUCCESS;
}

/**
 * Load / Reload configure file
 *
 * @param string                                Configuration filename
 *
 * @param int                                   Return value
 */
int conf_load(const char *filename)
{
    static char line[MAX_CONF_LINE_LENGTH];
    FILE *fp = fopen(filename, "r");
    if (!fp)
    {
        // Cannot open configure file
        debug_info("Configure file %s open failed\n", filename);
        return RTN_ERROR_IO_OPEN;
    }

    while (!feof(fp))
    {
        if (fgets(line, MAX_CONF_LINE_LENGTH, fp) && strlen(line))
        {
            conf_parse_line(line);
        }
    }

    fclose(fp);
    debug_info("Configure file %s loaded\n", filename);

    return RTN_SUCCESS;
}

/**
 * Set a configure item
 *
 * @param string key                            Configure variable
 * @param string value                          Configure value
 */
void conf_set(char *key, char *value, int level)
{
    char modified = 0x0;
    int hash_key = (int) hash(key, strlen(key)) % CONF_HASH_SIZE;
    struct param_t *last = &conf_list[hash_key];
    struct param_t *head = last->next, **tmp;

    if (!key)
    {
        return;
    }

    if (!value)
    {
        value = "";
    }

    pthread_mutex_lock(&conf_lock);

    while (head)
    {
        if (0 == strcmp(head->key, key))
        {
            if (level >= head->level)
            {
                if (head->value)
                {
                    free(head->value);
                }

                head->value = strdup(value);
                head->level = level;
            }
            
            modified = 0x1;

            break;
        }

        last = head;
        head = head->next;
    }

    if (!modified)
    {
        // Add new node
        head = malloc(sizeof(struct param_t));
        if (!head)
        {
            return;
        }

        head->key = strdup(key);
        head->value = strdup(value);
        head->level = level;
        head->next = NULL;
        last->next = head;

        while (conf_total >= conf_index_size)
        {
            tmp = realloc(conf_index, sizeof(struct param_t *) * conf_index_size * 2);
            if (!tmp)
            {
                continue;
            }

            conf_index = tmp;
            conf_index_size *= 2;
        }

        conf_index[conf_total ++] = head;
    }

    pthread_mutex_unlock(&conf_lock);
    
    return;
}

/**
 * Get configure value by given key
 *
 * @param string key                            Configure variale
 *
 * @return string                               Return configure value
 */
char * conf_get(char *key)
{
    char *value = NULL;
    int hash_key;
    struct param_t *curr = NULL;
    
    if (key)
    {
        hash_key = (int) hash(key, strlen(key)) % CONF_HASH_SIZE;
        curr = conf_list[hash_key].next;

        while (curr)
        {
            if (0 == strcmp(key, curr->key))
            {
                value = curr->value;
                break;
            }

            curr = curr->next;
        }
    }

    return value;
}

/**
 * Return each configure item
 *
 * @param int                                   0 for restart, 1 for next item
 *
 * @return param                                Configure item, NULL for end of all
 */
struct param_t ** conf_traversal(int *l)
{
    *l = conf_total;

    return conf_index;
}

/**
 * Parse one given line into k => v array, and store the pair into list
 * Source data will be break
 *
 * @param string line                           Input line
 */
void conf_parse_line(char *line)
{
    int l = strlen(line);
    int ct = 0;
    int s_pos = 0;
    int l_pos = -1;
    int in_space = 1;
    int has_key = 0;
    int in_quote = 0;

    char *key, *value;

    key = value = NULL;

    for (ct = 0; ct < l; ct ++)
    {
        if (line[ct] == '\'')
        {
            if (0 == in_quote)
            {
                in_quote = 1;
                line[ct] = 0x0;
            }

            else if (1 == in_quote)
            {
                in_quote = 0;
                line[ct] = 0x0;
            }
        }

        else if (line[ct] == '"')
        {
            if (0 == in_quote)
            {
                in_quote = 3;
                line[ct] = 0x0;
            }

            else if (3 == in_quote)
            {
                in_quote = 0;
                line[ct] = 0x0;
            }
        }

        if ((!in_quote && line[ct] <= 0x20) || (in_quote && line[ct] < 0x20))
        {
            in_space = 1;
            line[ct] = 0x0;
            
            if (l_pos > 0 && has_key)
            {
                // Value
                value = strdup(line + l_pos);
                conf_set(key, value, CONF_LEVEL_FILE);
                
                return;
            }
        }
        
        else if ((';' == line[ct] || '#' == line[ct]) && in_space)
        {
            return;
        }
        
        else if ('=' == line[ct] && 0 == has_key)
        {
            // Depart?
            line[ct] = 0x0;
            if (l_pos >= 0)
            {
                key = strdup(line + l_pos);
                has_key = 1;
                l_pos = -1;
                in_space = 1;
            }
        }
        
        else
        {
            // Legal ASCII
            if (1 == in_space)
            {
                l_pos = ct;
                in_space = 0;
                s_pos ++;
            }
        }
    }

    return;
}
